#include "InputSystem.h"
#include "EngineMessages.h"
#include "EngineLog.h"

InputSystem::InputSystem(EnginePointer<SettingsManager>& settings, EnginePointer<IMessageQueue>& messages)
:IEngineSystem(settings, messages)
{
	keyboardDevice = NULL;
	mouseDevice = NULL;
	windowSystem = NULL;
	initialized = FALSE;
	lastKeys = (BYTE*)malloc(256);
	currentKeys = (BYTE*)malloc(256);
	ZeroMemory(lastKeys, 256);
	ZeroMemory(currentKeys, 256);
}

InputSystem::~InputSystem(void)
{
	UnloadGraphicsContent();
	free(lastKeys);
	free(currentKeys);
	if (windowSystem.isValid()) windowSystem = NULL;
}

HRESULT InputSystem::LoadGraphicsContent()
{
	HRESULT hr = S_OK;
	LPDIRECTINPUT8 directInput = NULL;
	HINSTANCE hInst = NULL;

	HWND hWnd = windowSystem->getHWnd();

	hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);
	if (hInst == NULL)
	{
		hr = E_FAIL;
		goto END;
	}

	hr = DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&directInput, NULL);
	if (FAILED(hr)) goto END;

	hr = directInput->CreateDevice(GUID_SysKeyboard,& keyboardDevice, NULL);
	if (FAILED(hr)) goto END;

	hr = keyboardDevice->SetDataFormat(&c_dfDIKeyboard);
	if (FAILED(hr)) goto END;

	hr = keyboardDevice->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
	if (FAILED(hr)) goto END;

	hr = directInput->CreateDevice(GUID_SysMouse,& mouseDevice, NULL);
	if (FAILED(hr)) goto END;

	hr = mouseDevice->SetDataFormat(&c_dfDIMouse);
	if (FAILED(hr)) goto END;

	hr = mouseDevice->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
	if (FAILED(hr)) goto END;

	initialized = TRUE;

END:
	if (directInput != NULL) directInput->Release();

	if (FAILED(hr)) return hr;
	return S_OK;
}

void InputSystem::UnloadGraphicsContent()
{
	if (keyboardDevice != NULL) keyboardDevice->Release();
	keyboardDevice = NULL;
	if (mouseDevice != NULL) mouseDevice->Release();
	mouseDevice = NULL;

	initialized = FALSE;
}

HRESULT InputSystem::Initialize(EnginePointer<SystemCollection>& systems)
{
	windowSystem = (EnginePointer<WindowSystem>)systems->getSystem(WINDOW_SYSTEM_GUID);
	if (windowSystem == NULL)
		return E_FAIL;

	for (list<EnginePointer<InputComponent>>::iterator it = components.begin(); it != components.end(); it++)
	{
		(*it)->SetMessageQueue(messages);
	}

	return LoadGraphicsContent();
}

void InputSystem::Update(LONGLONG timeElapsed)
{
	HRESULT hr;

	if (SUCCEEDED(keyboardDevice->Acquire()) && SUCCEEDED(keyboardDevice->GetDeviceState(256, currentKeys)))
	{
		for (map<BYTE, list<EnginePointer<InputComponent>>>::iterator it = downBindings.begin(); it != downBindings.end(); it++)
		{
			if (!(lastKeys[(*it).first] & 0x80) && (currentKeys[(*it).first] & 0x80))
			{
				for (list<EnginePointer<InputComponent>>::iterator it2 = (*it).second.begin(); it2 != (*it).second.end(); it2++)
				{
					(*it2)->keyDown((*it).first, timeElapsed);
				}
			}
		}

		for (map<BYTE, list<EnginePointer<InputComponent>>>::iterator it = holdBindings.begin(); it != holdBindings.end(); it++)
		{
			if ((lastKeys[(*it).first] & 0x80) && (currentKeys[(*it).first] & 0x80))
			{
				for (list<EnginePointer<InputComponent>>::iterator it2 = (*it).second.begin(); it2 != (*it).second.end(); it2++)
				{
					(*it2)->keyHold((*it).first, timeElapsed);
				}
			}
		}

		for (map<BYTE, list<EnginePointer<InputComponent>>>::iterator it = upBindings.begin(); it != upBindings.end(); it++)
		{
			if ((lastKeys[(*it).first] & 0x80) && !(currentKeys[(*it).first] & 0x80))
			{
				for (list<EnginePointer<InputComponent>>::iterator it2 = (*it).second.begin(); it2 != (*it).second.end(); it2++)
				{
					(*it2)->keyUp((*it).first, timeElapsed);
				}
			}
		}

		BYTE* temp = lastKeys;
		lastKeys = currentKeys;
		currentKeys = temp;
		ZeroMemory(currentKeys, 256);
	}

	hr = mouseDevice->Acquire();
	if (SUCCEEDED(hr))
	{
	}
}

void InputSystem::Clean()
{
	UnloadGraphicsContent();
}

BOOL InputSystem::IsClean()
{
	return !initialized;
}

HRESULT InputSystem::RegisterComponent(EnginePointer<IEngineComponent>& component)
{
	HRESULT hr = S_OK;

	if (component->getSystemIdentifier() == getSystemIdentifier())
	{
		if (component->getComponentIdentifier() == INPUT_COMPONENT_GUID)
		{
			EnginePointer<InputComponent> iComponent = (EnginePointer<InputComponent>)component;
			BYTE* bindings;
			DWORD count;

			iComponent->getDownBindings(&bindings,& count);
			for (UINT i = 0; i < count; i++)
			{
				addDownBinding(bindings[i], iComponent);
			}

			iComponent->getHoldBindings(&bindings,& count);
			for (UINT i = 0; i < count; i++)
			{
				addHoldBinding(bindings[i], iComponent);
			}

			iComponent->getUpBindings(&bindings,& count);
			for (UINT i = 0; i < count; i++)
			{
				addUpBinding(bindings[i], iComponent);
			}
			if (initialized)
			{
				iComponent->SetMessageQueue(messages);
			}
			else
			{
				components.push_back(iComponent);
			}
		}
	}

	return hr;
}

void InputSystem::addDownBinding(BYTE key, EnginePointer<InputComponent> component)
{
	if (downBindings.find(key) == downBindings.end())
	{
		downBindings.insert(pair<BYTE, list<EnginePointer<InputComponent>>>(key, list<EnginePointer<InputComponent>>()));
	}
	downBindings[key].push_back(component);
}

void InputSystem::addHoldBinding(BYTE key, EnginePointer<InputComponent> component)
{
	if (holdBindings.find(key) == holdBindings.end())
	{
		holdBindings.insert(pair<BYTE, list<EnginePointer<InputComponent>>>(key, list<EnginePointer<InputComponent>>()));
	}
	holdBindings[key].push_back(component);
}

void InputSystem::addUpBinding(BYTE key, EnginePointer<InputComponent> component)
{
	if (upBindings.find(key) == upBindings.end())
	{
		upBindings.insert(pair<BYTE, list<EnginePointer<InputComponent>>>(key, list<EnginePointer<InputComponent>>()));
	}
	upBindings[key].push_back(component);
}

GUID InputSystem::getSystemIdentifier()
{
	return INPUT_SYSTEM_GUID;
}

list<GUID> InputSystem::getDependencies()
{
	list<GUID> dependencies;
	dependencies.push_back(WINDOW_SYSTEM_GUID);
	return dependencies;
}
